package raft

// 日志复制相关RPC
type AppendEntriesRequest struct {
	Term         int
	LeaderId     int
	PrevLogIndex int
	PrevLogTerm  int
	Entries      []Log
	LeaderCommit int
}

type AppendEntriesResponse struct {
	Term         int
	Success      bool
	ConflictTerm int
}

func (rf *Raft) AppendEntries(args *AppendEntriesRequest, reply *AppendEntriesResponse) {

	rf.mu.Lock()
	reply.Success = false
	DPrintf("%v receive appendEntries %v %v %v\n", rf.me, args.PrevLogIndex, args.PrevLogTerm, len(args.Entries))

	defer func() {
		lastLogIdx, lastLogTerm := rf.getLastLogIndexAndTerm()
		DPrintf("lastlogidx, lastlogterm %v %v", lastLogIdx, lastLogTerm)
		DPrintf("result : %v", reply.Success)
		rf.mu.Unlock()
		rf.persist()
	}()

	if args.Term < rf.term {
		//Reply false if term < currentTerm (§5.1))
		reply.Term = rf.term
		return
	} else {
		rf.changeState <- 0
		if args.Term > rf.term {
			rf.term = args.Term
			rf.votedFor = -1
		}
		reply.Term = rf.term
		// 先检查是否有匹配日志，无则返回ConflictTerm
		// 有则匹配日志开始检查（已提交日志必正确跳过）是否有冲突
		// 有冲突就删去后面所有日志
		// 没有日志就写入
		// 当没有log或者最新的可以直接匹配时，直接append
		matchIndex := -1
		if args.PrevLogIndex == rf.lastIncludedIndex {
			matchIndex = 0
		}
		for i, log := range rf.logs {
			if log.LogIndex == args.PrevLogIndex && log.Term == args.PrevLogTerm {
				matchIndex = i + 1
				break
			}
		}
		if matchIndex < 0 {
			// Reply false if log doesn’t contain an entry at prevLogIndex
			//whose term matches prevLogTerm (§5.3)
			reply.ConflictTerm = args.PrevLogTerm
			return
		} else {
			reply.Success = true
			for _, log := range args.Entries {
				if len(rf.logs)-1 < matchIndex {
					//Append any new entries not already in the log
					rf.logs = append(rf.logs, log)
				} else {
					//If an existing entry conflicts with a new one (same index
					//but different terms), delete the existing entry and all that
					//follow it (§5.3)
					if log.LogIndex != rf.logs[matchIndex].LogIndex || log.Term != rf.logs[matchIndex].Term {
						rf.logs = append(rf.logs[:matchIndex], log)
					}
				}
				matchIndex++
			}
			//If leaderCommit > commitIndex, set commitIndex =
			//min(leaderCommit, index of last new entry)
			if args.LeaderCommit > rf.commitIndex {
				lastLogIdx, _ := rf.getLastLogIndexAndTerm()
				rf.commitIndex = min(args.LeaderCommit, lastLogIdx)
				DPrintf("%v change commit %v", rf.me, rf.commitIndex)
			}
		}
		return
	}
}
